from ctypes import byref, c_buffer, c_int, cdll, string_at, WinDLL
from threading import Thread
import _queue, os, queue
import config, tones, winKernel
from .pcm import PcmPlayer
selfDir = os.path.dirname(__file__)
binDir = os.path.join(selfDir, "bin")
MSP_SUCCESS = 0
MSP_TTS_FLAG_STILL_HAVE_DATA = 1 # 音频还没取完，还有后继的音频
MSP_TTS_FLAG_DATA_END = 2 # 音频已经取完
class MscLogin:
    # 使用讯飞相关API需要先登录
    def __init__(self, appId):
        self.msvcr100 = cdll.LoadLibrary(os.path.join(binDir, 'msvcr100.dll'))
        self.msc = WinDLL(os.path.join(binDir, "msc.dll"))
        # 登录
        loginParams = f"appid = {appId},timeout = 5000, engine_start = tts, voice_name = xiaoyan, work_dir = {binDir}"
        ret = self.msc.MSPLogin(None, None, loginParams.encode('gbk'))
        assert ret == MSP_SUCCESS, "MSPLogin failed, error code is: %d" % ret
    def __del__(self):
        # 退出
        ret = self.msc.MSPLogout()
        assert ret == MSP_SUCCESS, "MSPLogout failed, error code is: %d" % ret
        del self.msc
        winKernel.kernel32.FreeLibrary(self.msvcr100._handle)
        del self.msvcr100
    def newSynthesizer(self):
        # 新建一个合成器对象
        return Synthesizer(self.msc)
class QTts: # 讯飞TTS类
    def __init__(self, dll, params):
        self.dll = dll
        ret = c_int()
        self.sessionID = self.dll.QTTSSessionBegin(params.encode(), byref(ret))
        assert MSP_SUCCESS == ret.value, "QTTSSessionBegin failed, error code is: %d" % ret.value
    def __del__(self): # 释放资源
        self.close()
    def close(self):
        # 关闭一个合成实例
        if not self.sessionID:return
        ret = self.dll.QTTSSessionEnd (self.sessionID, b"normal end")
        self.sessionID = None
        assert MSP_SUCCESS == ret, "QTTSSessionEnd failed, error code is: %d" % ret
        if hasattr(self, 'dll'):
            del self.dll
    def textPut(self, text, params = None):
        # 送入文本并合成
        data = text.encode(errors = 'xmlcharrefreplace')
        textLen = len(data)
        ret = self.dll.QTTSTextPut(self.sessionID, data, textLen, params.encode() if params else None)
        assert MSP_SUCCESS == ret, "QTTSTextPut failed, error code is: %d" % ret
    def audioGet(self):
        # 取回合成的PCM数据
        audioLen = c_int()
        synthStatus = c_int()
        ret = c_int()
        lpData = self.dll.QTTSAudioGet(self.sessionID, byref(audioLen), byref(synthStatus), byref(ret))
        if lpData:
            data = string_at(lpData, audioLen.value)
        else:
            data = None
        return MSP_TTS_FLAG_DATA_END == synthStatus.value or MSP_SUCCESS != ret.value, data
class Synthesizer: # 语音库合成类
    def run(self):
        # 创建播放器
        self.player = PcmPlayer(channels = 1, samplesPerSec = self.getSampleRate(), bitsPerSample = 16, wantDucking = False, outputDevice = config.conf["speech"]["outputDevice"])
        # 合成器的线程任务队列
        while hasattr(self, 'queue'):
            try: # 取出一条任务
                text, callback, params = self.queue.get(True, 0.01)
            except _queue.Empty:continue
            # 设置临时合成参数
            if 'tempo' in params.keys():
                self.player.setTempo(float(params['tempo']))
            else:
                self.player.setTempo(self.getTempo())
            oriParams = self.params.copy()
            if 'voice' in params.keys():
                self.setVoice(params['voice'])
            if 'pitch' in params.keys():
                self.setPitch(params['pitch'])
            if 'rate' in params.keys():
                self.setSpeed(params['rate'])
            if 'volume' in params.keys():
                self.setVolume(params['volume'])
            if 'effect' in params.keys():
                self.setEffect(params['effect'])
            if 'rdn' in params.keys():
                self.setRdn(params['rdn'])
            qTts = QTts(self.dll, self.getParams())
            self.params.update(oriParams)
            # 开始合成任务
            try:
                qTts.textPut(text)
            except AssertionError:
                tones.beep(500, 20)
                pass
            # 处理音频数据
            if self.handle(qTts):
                callback()
            try: # 播放完毕释放资源
                qTts.close()
            except AssertionError:pass
            del qTts
        # 线程结束释放资源
        self.player.close()
        del self.player
    def handle(self, qTts):
        # 处理音频数据
        self.player.play()
        data = self.waitForFrame(qTts)
        while data:
            if self.player.isStopped():
                return False
            if isinstance(data, bytes):
                self.player.write(data)
            else:
                self.player.flush()
                return True
            data = self.waitForFrame(qTts)
    def getParams(self):
        # 获取合成参数
        return ', '.join([f'{k} = {v}' for k, v in self.params.items()])
    def waitForFrame(self, qTts):
        # 等待合成的一帧数据
        while True:
            isFinal, audio = qTts.audioGet()
            if audio:
                return audio
            if isFinal:return True
    def __init__(self, dll):
        # 初始化
        self.dll = dll
        self.params = {}
        self.setEngineType(self.getEngineType())
        self.setTtsResPath(self.getTtsResPath())
        self.setSampleRate(self.getSampleRate())
        self.setTextEncoding(self.getTextEncoding())
        self.setTempo(1.0)
        # 注意:没有默认值的参数必须由外部设定其值
        self.queue = queue.Queue()
        self.thread = Thread(target = self.run)
        self.thread.start()
    def setTempo(self, value):
        self.tempo = value
    def getTempo(self):
        return float(self.tempo)
    def speak(self, text, callback = None, **params):
        # 朗读函数
        if hasattr(self, 'queue'):
            self.queue.put((text, callback, params,))
    def stop(self):
        if hasattr(self, 'player'):
            self.player.stop()
        if hasattr(self, 'queue'):
            self.queue.queue.clear()
    def pause(self, switch):
        # 暂停朗读与恢复朗读的函数
        self.player.pause(switch)
    def __del__(self):
        # 释放资源
        self.terminate()
    def terminate(self):
        self.stop()
        if hasattr(self, 'thread'):
            del self.thread
        if hasattr(self, 'queue'):
            del self.queue
        if hasattr(self, 'dll'):
            del self.dll
    def setEngineType(self, value):
        '''通用-引擎类型
        可取值：
        cloud：在线引擎
        local：离线引擎，默认为cloud
        '''
        self.params['engine_type'] = value
    def getEngineType(self):
        return self.params['engine_type'] if 'engine_type' in self.params.keys() else 'local'
    def setVoice(self, value):
        '''通用-发音人
        不同的发音人代表了不同的音色，
        如男声、女声、童声等，具体参数值请到控制台-发音人授权管理确认
        '''
        if not value or value.find('-') < 0:return
        engineType, name = value.split('-')
        self.params['voice_name'] = name
        self.setEngineType(engineType)
        self.setTtsResPath(f'fo|res\\tts\\{name}.jet;fo|res\\tts\\common.jet')
    def getVoice(self):
        engineType = self.params['engine_type'] if 'engine_type' in self.params.keys() else 'local'
        return engineType + '-' + (self.params['voice_name'] if 'voice_name' in self.params.keys() else 'xiaoyan')
    def setSpeed(self, value):
        '''通用-语速
        合成音频对应的语速，
        取值范围：[0,100]，数值越大语速越快。
        默认值：50
        '''
        self.params['speed'] = value
    def getSpeed(self):
        return self.params['speed'] if 'speed' in self.params.keys() else 50
    def setVolume(self, value):
        '''通用-音量
        合成音频的音量，
        取值范围：[0,100]，数值越大音量越大。
        默认值：50
        '''
        self.params['volume'] = value
    def getVolume(self):
        return self.params['volume'] if 'volume' in self.params.keys() else 50
    def setPitch(self, value):
        '''通用-语调
        合成音频的音调，
        取值范围：[0,100]，数值越大音调越高。
        默认值：50
        '''
        self.params['pitch'] = value
    def getPitch(self):
        return self.params['pitch'] if 'pitch' in self.params.keys() else 50
    def setTtsResPath(self, value):
        '''离线-合成资源路径
        合成资源所在路径，支持fo 方式参数设置，对应格式如下：
        fo|[file_info]|[offset]|[length]
        （1）若是合并资源，则只需传入一个资源路径，如：fo| combined.jet|0|1024
        （2）若是分离资源，则需传两个资源路径，如：fo|common.jet|0|1024;fo| xiaoyan.jet|0|1024
        '''
        self.params['tts_res_path'] = value
    def getTtsResPath(self):
        return self.params['tts_res_path'] if 'tts_res_path' in self.params.keys() else 'fo|res\\tts\\xiaoyan.jet;fo|res\\tts\\common.jet'
    def setRdn(self, value):
        '''通用-数字发音
        合成音频数字发音，支持参数，
        0 数值优先,
        1 完全数值,
        2 完全字符串，
        3 字符串优先，
        默认值：0
        '''
        self.params['rdn'] = value
    def getRdn(self):
        return self.params['rdn'] if 'rdn' in self.params.keys() else 0
    def getRdns(self):
        rdns = {
          0: '数值优先',
          1: '完全数值',
          2: '一律文本',
          3: '文本优先'
        }
        return rdns.items()
    def setRcn(self, value):
        '''离线-1 的中文发音
        支持参数：
        0：表示发音为yao
        1：表示发音为yi
        默认值：0
        '''
        self.params['rcn'] = value
    def getRcn(self):
        return self.params['rcn'] if 'rcn' in self.params.keys() else 0
    def setTextEncoding(self, value):
        '''通用-文本编码格式（必传）
        合成文本编码格式，支持参数，GB2312，GBK，BIG5，UNICODE，GB18030，UTF8
        '''
        self.params['text_encoding'] = value
    def getTextEncoding(self):
        return self.params['text_encoding'] if 'text_encoding' in self.params.keys() else 'UTF8'
    def setSampleRate(self, value):
        '''通用-合成音频采样率
        合成音频采样率，支持参数，16000，8000（离线高品质合成暂不支持），默认为16000
        '''
        self.params['sample_rate'] = value
    def getSampleRate(self):
        return self.params['sample_rate'] if 'sample_rate' in self.params.keys() else 16000
    def setBackgroundSound(self, value):
        '''在线-背景音
        合成音频中的背景音，支持参数，
        0：无背景音乐，
        1：有背景音乐
        '''
        self.params['background_sound'] = value
    def getBackgroundSound(self):
        return self.params['background_sound'] if 'background_sound' in self.params.keys() else 0
    def setAue(self, value):
        '''在线-音频编码格式和压缩等级
        码算法：raw；speex；speex-wb；ico
        编码等级：raw：不进行解压缩 speex系列：0-10；
        默认为speex-wb;7
        speex对应sample_rate=8000
        speex-wb对应sample_rate=16000
        ico对应sample_rate=16000
        '''
        self.params['aue'] = value
    def getAue(self):
        return self.params['aue'] if 'aue' in self.params.keys() else 'speex-wb'
    def setTtp(self, value):
        '''在线-文本类型
        合成文本类型，支持参数，
        text: 普通格式文本
        cssml：cssml 格式文本
        默认值：text
        '''
        self.params['ttp'] = value
    def getTtp(self):
        return self.params['ttp'] if 'ttp' in self.params.keys() else 'text'
    def setSpeedIncrease(self, value):
        '''离线-语速增强
        通过设置此参数控制合成音频语速基数，取值范围，
        1：正常 2：2 倍语速 4：4 倍语速
        '''
        self.params['speed_increase'] = value
    def getSpeedIncrease(self):
        return self.params['speed_increase'] if 'speed_increase' in self.params.keys() else 1
    def getSpeedIncreases(self):
        values = {
          1: '一倍',
          2: '两倍',
          4: '四倍'
        }
        return values.items()
    def setEffect(self, value):
        '''离线-合成音效
        合成音频的音效，取值范围，
        0 无音效，1 忽远忽近，2 回声，3 机器人，4 合唱，5 水下，6 混响，7 阴阳怪气
        '''
        self.params['effect'] = value
    def getEffect(self):
        return self.params['effect'] if 'effect' in self.params.keys() else 0
    def getEffects(self):
        effects = {
          0: '无音效',
          1: '忽远忽近',
          2: '回声',
          3: '机器人',
          4: '合唱',
          5: '水下',
          6: '混响',
          7: '阴阳怪气'
        }
        return effects.items()